﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
using System.Web.Configuration;
using System.Security.Permissions;
using KgUtils.Web;
using KgUtils.Collections;
using System.Configuration;


namespace KgUtils.ActiveDirectory
{
    public class DirectoryManager
    {
        protected enum SearchType { PartialFilter, ExactFilter };
        protected static object SyncRoot = new Object();

        protected ActiveDirectoryConnection ADConnection { get; set; }

        public void Initialize(ActiveDirectoryConnection adConnection)
        {
            ADConnection = adConnection;
        }

        public List<ActiveDirectoryUser> GetAllADUsers(int? startRow, int? pageSize, SortInfo sorting)
        {
            return GetADUsers(null, SearchType.PartialFilter, startRow, pageSize, sorting);
        }

        public List<ActiveDirectoryUser> GetADUsersByFilter(string filter, int? startRow, int? pageSize, SortInfo sorting)
        {
            return GetADUsers(filter, SearchType.PartialFilter, startRow, pageSize, sorting);
        }

        public ActiveDirectoryUser GetADUserById(string id)
        {
            return GetADUsers(id, SearchType.ExactFilter).FirstOrDefault();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="searchType"></param>
        /// <param name="startRow"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        protected List<ActiveDirectoryUser> GetADUsers(string filter, SearchType searchType = SearchType.PartialFilter, int? startRow = null, int? pageSize = null, SortInfo sorting = null)
        {
            lock (SyncRoot)
            {
                return GetADUsersFromCache(filter, searchType, startRow, pageSize, sorting);
            }
        }

        protected List<ActiveDirectoryUser> GetADUsersFromCache(string filter, SearchType searchType = SearchType.PartialFilter, int? startRow = null, int? pageSize = null, SortInfo sorting = null)
        {
            var cachedUsers = CacheManager.Instance.GetCache<CachedUsers>(Constants.CachingADUsers);
            if (cachedUsers == null || cachedUsers.LastRefresh.Date != DateTime.Today)
            {
                //reload cache :
                if (cachedUsers == null)
                    cachedUsers = new CachedUsers();

                cachedUsers.UserList = GetADUsersFromAD();

                cachedUsers.LastRefresh = DateTime.Now;
                CacheManager.Instance.SetCache(Constants.CachingADUsers, cachedUsers);
            }

            //filters the results :
            List<ActiveDirectoryUser> results;
            if (String.IsNullOrWhiteSpace(filter))
            {
                results = cachedUsers.UserList;
            }
            else
            {
                if (searchType == SearchType.PartialFilter)
                    results = cachedUsers.UserList.Where(u => u.LastName.ToLower().StartsWith(filter.ToLower())).ToList();
                else
                    results = cachedUsers.UserList.Where(u => u.AccountName.ToLower() == filter.ToLower()).ToList();
            }

            if (sorting != null && !String.IsNullOrWhiteSpace(sorting.SortExpression))
            {
                results = results.SortBy(sorting.SortExpression, sorting.SortDirection).ToList();
            }

            if (startRow.HasValue && pageSize.HasValue)
                return results.Skip(startRow.Value).Take(pageSize.Value).ToList();
            else
                return results;
        }

        internal List<ActiveDirectoryUser> GetADUsersFromAD()
        {
            using (DirectorySearcher searcher = GetDirectorySearcher())
            {
                searcher.SearchScope = SearchScope.Subtree;
                searcher.ReferralChasing = ReferralChasingOption.All;

                searcher.Filter = BuildFilter(null, SearchType.PartialFilter);

                searcher.PageSize = int.MaxValue;
                searcher.PropertiesToLoad.AddRange(BuildPropertiesFilter());

                using (SearchResultCollection results = searcher.FindAll())
                {
                    var parsedUsers = ParseResults(results);
                    return parsedUsers.ToList();
                }
            }
        }

        protected string GetPropertyInformation(ResultPropertyCollection properties, string propertyName)
        {
            if (properties.Contains(propertyName))
                return properties[propertyName][0].ToString();
            else
                return null;
        }

        protected DirectorySearcher GetDirectorySearcher()
        {
            DirectoryEntry adRoot = new DirectoryEntry(ADConnection.ConnectionString, ADConnection.Login, ADConnection.Password, AuthenticationTypes.Secure);
            return new DirectorySearcher(adRoot);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="nameFilter"></param>
        /// <returns></returns>
        /// <remarks>Search filter syntax : http://msdn.microsoft.com/fr-fr/library/aa746475%28v=VS.85%29.aspx </remarks>
        protected string BuildFilter(string nameFilter, SearchType searchType)
        {
            if (String.IsNullOrWhiteSpace(nameFilter))
                return "(&(objectCategory=user)(sn=*))";
            else
            {
                if (searchType == SearchType.PartialFilter)
                    return String.Format("(&(objectCategory=user)(sn={0}*))", nameFilter);
                else
                    return String.Format("(&(objectCategory=user)(samaccountname={0}))", nameFilter);
            }
        }

        protected string[] BuildPropertiesFilter()
        {
            return new string[]
            {
                "userprincipalname",
                "givenname",
                "samaccountname",
                "displayname",
                "logoncount",
                "name",
                "sn",
                "mail"
            };
        }

        protected ActiveDirectoryUser ParseResult(SearchResult result)
        {
            string userprincipalname;//principal username (looks like email)
            string email; //email
            string givenname;//prenom
            string samaccountname;//account
            string displayname;//full name
            string logoncount;//nbconnexions
            string name;//full name
            string sn; //last name

            userprincipalname = GetPropertyInformation(result.Properties, "userprincipalname");
            givenname = GetPropertyInformation(result.Properties, "givenname");
            samaccountname = GetPropertyInformation(result.Properties, "samaccountname");
            displayname = GetPropertyInformation(result.Properties, "displayname");
            logoncount = GetPropertyInformation(result.Properties, "logoncount");
            name = GetPropertyInformation(result.Properties, "name");
            sn = GetPropertyInformation(result.Properties, "sn");
            email = GetPropertyInformation(result.Properties, "email");

            if (displayname != null && userprincipalname != null)
            {
                var newUser = new ActiveDirectoryUser();
                newUser.AccountName = samaccountname;
                newUser.Email = email;
                newUser.UserPrincipalName = userprincipalname;
                newUser.FirstName = givenname;
                newUser.FullName = displayname;
                newUser.LastName = sn;
                return newUser;
            }
            else
                return null;
        }

        protected List<ActiveDirectoryUser> ParseResults(SearchResultCollection results)
        {
            var usersResult = new List<ActiveDirectoryUser>();
            foreach (SearchResult result in results)
            {
                var user = ParseResult(result);
                if (user != null)
                    usersResult.Add(user);
            }

            return usersResult;
        }

        #region Singleton
        private static readonly DirectoryManager instance = new DirectoryManager();
        public static DirectoryManager Instance
        {
            get { return instance; }
        }

        private DirectoryManager()
        {
        }
        #endregion
    }
}
